<html>

<head>
<title>Manic Digger</title>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<link rel="icon" href="favicon.ico" type="image/vnd.microsoft.icon">

<script type="text/javascript" src="ManicDigger.js"></script>

<script id="shader-fs" type="x-shader/x-fragment">
    precision mediump float;

    varying vec2 vTextureCoord;
    varying vec4 vColor;

    uniform sampler2D uSampler;

    void main(void) {
        vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
        if (textureColor.a < 0.1)
        {
         discard;
        }
        else
        {
         gl_FragColor = textureColor * vColor;
        }
    }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    attribute vec4 aVertexColor;
    attribute vec2 aTextureCoord;

    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    varying vec2 vTextureCoord;
    varying vec4 vColor;

    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        vTextureCoord = aTextureCoord;
        vColor = aVertexColor;
    }
</script>

<script type="text/javascript">
/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function n(e){throw e;}var q=void 0,aa=this;function r(e,c){var d=e.split("."),b=aa;!(d[0]in b)&&b.execScript&&b.execScript("var "+d[0]);for(var a;d.length&&(a=d.shift());)!d.length&&c!==q?b[a]=c:b=b[a]?b[a]:b[a]={}};var u="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;new (u?Uint8Array:Array)(256);var v;for(v=0;256>v;++v)for(var w=v,ba=7,w=w>>>1;w;w>>>=1)--ba;function x(e,c,d){var b,a="number"===typeof c?c:c=0,f="number"===typeof d?d:e.length;b=-1;for(a=f&7;a--;++c)b=b>>>8^z[(b^e[c])&255];for(a=f>>3;a--;c+=8)b=b>>>8^z[(b^e[c])&255],b=b>>>8^z[(b^e[c+1])&255],b=b>>>8^z[(b^e[c+2])&255],b=b>>>8^z[(b^e[c+3])&255],b=b>>>8^z[(b^e[c+4])&255],b=b>>>8^z[(b^e[c+5])&255],b=b>>>8^z[(b^e[c+6])&255],b=b>>>8^z[(b^e[c+7])&255];return(b^4294967295)>>>0}
var A=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,
2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,
2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,
2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,
3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,
936918E3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117],z=u?new Uint32Array(A):A;function B(){}B.prototype.getName=function(){return this.name};B.prototype.getData=function(){return this.data};B.prototype.H=function(){return this.I};r("Zlib.GunzipMember",B);r("Zlib.GunzipMember.prototype.getName",B.prototype.getName);r("Zlib.GunzipMember.prototype.getData",B.prototype.getData);r("Zlib.GunzipMember.prototype.getMtime",B.prototype.H);function D(e){var c=e.length,d=0,b=Number.POSITIVE_INFINITY,a,f,g,k,m,p,t,h,l,y;for(h=0;h<c;++h)e[h]>d&&(d=e[h]),e[h]<b&&(b=e[h]);a=1<<d;f=new (u?Uint32Array:Array)(a);g=1;k=0;for(m=2;g<=d;){for(h=0;h<c;++h)if(e[h]===g){p=0;t=k;for(l=0;l<g;++l)p=p<<1|t&1,t>>=1;y=g<<16|h;for(l=p;l<a;l+=m)f[l]=y;++k}++g;k<<=1;m<<=1}return[f,d,b]};var E=[],F;for(F=0;288>F;F++)switch(!0){case 143>=F:E.push([F+48,8]);break;case 255>=F:E.push([F-144+400,9]);break;case 279>=F:E.push([F-256+0,7]);break;case 287>=F:E.push([F-280+192,8]);break;default:n("invalid literal: "+F)}
var ca=function(){function e(a){switch(!0){case 3===a:return[257,a-3,0];case 4===a:return[258,a-4,0];case 5===a:return[259,a-5,0];case 6===a:return[260,a-6,0];case 7===a:return[261,a-7,0];case 8===a:return[262,a-8,0];case 9===a:return[263,a-9,0];case 10===a:return[264,a-10,0];case 12>=a:return[265,a-11,1];case 14>=a:return[266,a-13,1];case 16>=a:return[267,a-15,1];case 18>=a:return[268,a-17,1];case 22>=a:return[269,a-19,2];case 26>=a:return[270,a-23,2];case 30>=a:return[271,a-27,2];case 34>=a:return[272,
a-31,2];case 42>=a:return[273,a-35,3];case 50>=a:return[274,a-43,3];case 58>=a:return[275,a-51,3];case 66>=a:return[276,a-59,3];case 82>=a:return[277,a-67,4];case 98>=a:return[278,a-83,4];case 114>=a:return[279,a-99,4];case 130>=a:return[280,a-115,4];case 162>=a:return[281,a-131,5];case 194>=a:return[282,a-163,5];case 226>=a:return[283,a-195,5];case 257>=a:return[284,a-227,5];case 258===a:return[285,a-258,0];default:n("invalid length: "+a)}}var c=[],d,b;for(d=3;258>=d;d++)b=e(d),c[d]=b[2]<<24|b[1]<<
16|b[0];return c}();u&&new Uint32Array(ca);function G(e,c){this.i=[];this.j=32768;this.d=this.f=this.c=this.n=0;this.input=u?new Uint8Array(e):e;this.o=!1;this.k=H;this.z=!1;if(c||!(c={}))c.index&&(this.c=c.index),c.bufferSize&&(this.j=c.bufferSize),c.bufferType&&(this.k=c.bufferType),c.resize&&(this.z=c.resize);switch(this.k){case I:this.a=32768;this.b=new (u?Uint8Array:Array)(32768+this.j+258);break;case H:this.a=0;this.b=new (u?Uint8Array:Array)(this.j);this.e=this.F;this.q=this.B;this.l=this.D;break;default:n(Error("invalid inflate mode"))}}
var I=0,H=1;
G.prototype.g=function(){for(;!this.o;){var e=J(this,3);e&1&&(this.o=!0);e>>>=1;switch(e){case 0:var c=this.input,d=this.c,b=this.b,a=this.a,f=c.length,g=q,k=q,m=b.length,p=q;this.d=this.f=0;d+1>=f&&n(Error("invalid uncompressed block header: LEN"));g=c[d++]|c[d++]<<8;d+1>=f&&n(Error("invalid uncompressed block header: NLEN"));k=c[d++]|c[d++]<<8;g===~k&&n(Error("invalid uncompressed block header: length verify"));d+g>c.length&&n(Error("input buffer is broken"));switch(this.k){case I:for(;a+g>b.length;){p=
m-a;g-=p;if(u)b.set(c.subarray(d,d+p),a),a+=p,d+=p;else for(;p--;)b[a++]=c[d++];this.a=a;b=this.e();a=this.a}break;case H:for(;a+g>b.length;)b=this.e({t:2});break;default:n(Error("invalid inflate mode"))}if(u)b.set(c.subarray(d,d+g),a),a+=g,d+=g;else for(;g--;)b[a++]=c[d++];this.c=d;this.a=a;this.b=b;break;case 1:this.l(da,ea);break;case 2:fa(this);break;default:n(Error("unknown BTYPE: "+e))}}return this.q()};
var K=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],L=u?new Uint16Array(K):K,N=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],O=u?new Uint16Array(N):N,P=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],Q=u?new Uint8Array(P):P,R=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],ga=u?new Uint16Array(R):R,ha=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,
13,13],U=u?new Uint8Array(ha):ha,V=new (u?Uint8Array:Array)(288),W,ia;W=0;for(ia=V.length;W<ia;++W)V[W]=143>=W?8:255>=W?9:279>=W?7:8;var da=D(V),X=new (u?Uint8Array:Array)(30),Y,ja;Y=0;for(ja=X.length;Y<ja;++Y)X[Y]=5;var ea=D(X);function J(e,c){for(var d=e.f,b=e.d,a=e.input,f=e.c,g=a.length,k;b<c;)f>=g&&n(Error("input buffer is broken")),d|=a[f++]<<b,b+=8;k=d&(1<<c)-1;e.f=d>>>c;e.d=b-c;e.c=f;return k}
function Z(e,c){for(var d=e.f,b=e.d,a=e.input,f=e.c,g=a.length,k=c[0],m=c[1],p,t;b<m&&!(f>=g);)d|=a[f++]<<b,b+=8;p=k[d&(1<<m)-1];t=p>>>16;e.f=d>>t;e.d=b-t;e.c=f;return p&65535}
function fa(e){function c(a,c,b){var d,e=this.w,f,g;for(g=0;g<a;)switch(d=Z(this,c),d){case 16:for(f=3+J(this,2);f--;)b[g++]=e;break;case 17:for(f=3+J(this,3);f--;)b[g++]=0;e=0;break;case 18:for(f=11+J(this,7);f--;)b[g++]=0;e=0;break;default:e=b[g++]=d}this.w=e;return b}var d=J(e,5)+257,b=J(e,5)+1,a=J(e,4)+4,f=new (u?Uint8Array:Array)(L.length),g,k,m,p;for(p=0;p<a;++p)f[L[p]]=J(e,3);if(!u){p=a;for(a=f.length;p<a;++p)f[L[p]]=0}g=D(f);k=new (u?Uint8Array:Array)(d);m=new (u?Uint8Array:Array)(b);e.w=
0;e.l(D(c.call(e,d,g,k)),D(c.call(e,b,g,m)))}G.prototype.l=function(e,c){var d=this.b,b=this.a;this.r=e;for(var a=d.length-258,f,g,k,m;256!==(f=Z(this,e));)if(256>f)b>=a&&(this.a=b,d=this.e(),b=this.a),d[b++]=f;else{g=f-257;m=O[g];0<Q[g]&&(m+=J(this,Q[g]));f=Z(this,c);k=ga[f];0<U[f]&&(k+=J(this,U[f]));b>=a&&(this.a=b,d=this.e(),b=this.a);for(;m--;)d[b]=d[b++-k]}for(;8<=this.d;)this.d-=8,this.c--;this.a=b};
G.prototype.D=function(e,c){var d=this.b,b=this.a;this.r=e;for(var a=d.length,f,g,k,m;256!==(f=Z(this,e));)if(256>f)b>=a&&(d=this.e(),a=d.length),d[b++]=f;else{g=f-257;m=O[g];0<Q[g]&&(m+=J(this,Q[g]));f=Z(this,c);k=ga[f];0<U[f]&&(k+=J(this,U[f]));b+m>a&&(d=this.e(),a=d.length);for(;m--;)d[b]=d[b++-k]}for(;8<=this.d;)this.d-=8,this.c--;this.a=b};
G.prototype.e=function(){var e=new (u?Uint8Array:Array)(this.a-32768),c=this.a-32768,d,b,a=this.b;if(u)e.set(a.subarray(32768,e.length));else{d=0;for(b=e.length;d<b;++d)e[d]=a[d+32768]}this.i.push(e);this.n+=e.length;if(u)a.set(a.subarray(c,c+32768));else for(d=0;32768>d;++d)a[d]=a[c+d];this.a=32768;return a};
G.prototype.F=function(e){var c,d=this.input.length/this.c+1|0,b,a,f,g=this.input,k=this.b;e&&("number"===typeof e.t&&(d=e.t),"number"===typeof e.A&&(d+=e.A));2>d?(b=(g.length-this.c)/this.r[2],f=258*(b/2)|0,a=f<k.length?k.length+f:k.length<<1):a=k.length*d;u?(c=new Uint8Array(a),c.set(k)):c=k;return this.b=c};
G.prototype.q=function(){var e=0,c=this.b,d=this.i,b,a=new (u?Uint8Array:Array)(this.n+(this.a-32768)),f,g,k,m;if(0===d.length)return u?this.b.subarray(32768,this.a):this.b.slice(32768,this.a);f=0;for(g=d.length;f<g;++f){b=d[f];k=0;for(m=b.length;k<m;++k)a[e++]=b[k]}f=32768;for(g=this.a;f<g;++f)a[e++]=c[f];this.i=[];return this.buffer=a};
G.prototype.B=function(){var e,c=this.a;u?this.z?(e=new Uint8Array(c),e.set(this.b.subarray(0,c))):e=this.b.subarray(0,c):(this.b.length>c&&(this.b.length=c),e=this.b);return this.buffer=e};function $(e){this.input=e;this.c=0;this.m=[];this.s=!1}$.prototype.G=function(){this.s||this.g();return this.m.slice()};
$.prototype.g=function(){for(var e=this.input.length;this.c<e;){var c=new B,d=q,b=q,a=q,f=q,g=q,k=q,m=q,p=q,t=q,h=this.input,l=this.c;c.u=h[l++];c.v=h[l++];(31!==c.u||139!==c.v)&&n(Error("invalid file signature:"+c.u+","+c.v));c.p=h[l++];switch(c.p){case 8:break;default:n(Error("unknown compression method: "+c.p))}c.h=h[l++];p=h[l++]|h[l++]<<8|h[l++]<<16|h[l++]<<24;c.I=new Date(1E3*p);c.O=h[l++];c.N=h[l++];0<(c.h&4)&&(c.J=h[l++]|h[l++]<<8,l+=c.J);if(0<(c.h&8)){m=[];for(k=0;0<(g=h[l++]);)m[k++]=String.fromCharCode(g);
c.name=m.join("")}if(0<(c.h&16)){m=[];for(k=0;0<(g=h[l++]);)m[k++]=String.fromCharCode(g);c.K=m.join("")}0<(c.h&2)&&(c.C=x(h,0,l)&65535,c.C!==(h[l++]|h[l++]<<8)&&n(Error("invalid header crc16")));d=h[h.length-4]|h[h.length-3]<<8|h[h.length-2]<<16|h[h.length-1]<<24;h.length-l-4-4<512*d&&(f=d);b=new G(h,{index:l,bufferSize:f});c.data=a=b.g();l=b.c;c.L=t=(h[l++]|h[l++]<<8|h[l++]<<16|h[l++]<<24)>>>0;x(a,q,q)!==t&&n(Error("invalid CRC-32 checksum: 0x"+x(a,q,q).toString(16)+" / 0x"+t.toString(16)));c.M=
d=(h[l++]|h[l++]<<8|h[l++]<<16|h[l++]<<24)>>>0;(a.length&4294967295)!==d&&n(Error("invalid input size: "+(a.length&4294967295)+" / "+d));this.m.push(c);this.c=l}this.s=!0;var y=this.m,s,M,S=0,T=0,C;s=0;for(M=y.length;s<M;++s)T+=y[s].data.length;if(u){C=new Uint8Array(T);for(s=0;s<M;++s)C.set(y[s].data,S),S+=y[s].data.length}else{C=[];for(s=0;s<M;++s)C[s]=y[s].data;C=Array.prototype.concat.apply([],C)}return C};r("Zlib.Gunzip",$);r("Zlib.Gunzip.prototype.decompress",$.prototype.g);r("Zlib.Gunzip.prototype.getMembers",$.prototype.G);}).call(this); //# sourceMappingURL=gunzip.min.js.map
</script>

<script type = "text/javascript">
/*
 * Extracted from pdf.js
 * https://github.com/andreasgal/pdf.js
 *
 * Copyright (c) 2011 Mozilla Foundation
 *
 * Contributors: Andreas Gal <gal@mozilla.com>
 *               Chris G Jones <cjones@mozilla.com>
 *               Shaon Barman <shaon.barman@gmail.com>
 *               Vivien Nicolas <21@vingtetun.org>
 *               Justin D'Arcangelo <justindarc@gmail.com>
 *               Yury Delendik
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

var DecodeStream = (function() {
  function constructor() {
    this.pos = 0;
    this.bufferLength = 0;
    this.eof = false;
    this.buffer = null;
  }

  constructor.prototype = {
    ensureBuffer: function decodestream_ensureBuffer(requested) {
      var buffer = this.buffer;
      var current = buffer ? buffer.byteLength : 0;
      if (requested < current)
        return buffer;
      var size = 512;
      while (size < requested)
        size <<= 1;
      var buffer2 = new Uint8Array(size);
      for (var i = 0; i < current; ++i)
        buffer2[i] = buffer[i];
      return this.buffer = buffer2;
    },
    getByte: function decodestream_getByte() {
      var pos = this.pos;
      while (this.bufferLength <= pos) {
        if (this.eof)
          return null;
        this.readBlock();
      }
      return this.buffer[this.pos++];
    },
    getBytes: function decodestream_getBytes(length) {
      var pos = this.pos;

      if (length) {
        this.ensureBuffer(pos + length);
        var end = pos + length;

        while (!this.eof && this.bufferLength < end)
          this.readBlock();

        var bufEnd = this.bufferLength;
        if (end > bufEnd)
          end = bufEnd;
      } else {
        while (!this.eof)
          this.readBlock();

        var end = this.bufferLength;
      }

      this.pos = end;
      return this.buffer.subarray(pos, end);
    },
    lookChar: function decodestream_lookChar() {
      var pos = this.pos;
      while (this.bufferLength <= pos) {
        if (this.eof)
          return null;
        this.readBlock();
      }
      return String.fromCharCode(this.buffer[this.pos]);
    },
    getChar: function decodestream_getChar() {
      var pos = this.pos;
      while (this.bufferLength <= pos) {
        if (this.eof)
          return null;
        this.readBlock();
      }
      return String.fromCharCode(this.buffer[this.pos++]);
    },
    makeSubStream: function decodestream_makeSubstream(start, length, dict) {
      var end = start + length;
      while (this.bufferLength <= end && !this.eof)
        this.readBlock();
      return new Stream(this.buffer, start, length, dict);
    },
    skip: function decodestream_skip(n) {
      if (!n)
        n = 1;
      this.pos += n;
    },
    reset: function decodestream_reset() {
      this.pos = 0;
    }
  };

  return constructor;
})();

var FlateStream = (function() {
  var codeLenCodeMap = new Uint32Array([
    16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
  ]);

  var lengthDecode = new Uint32Array([
    0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a,
    0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f,
    0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073,
    0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102
  ]);

  var distDecode = new Uint32Array([
    0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d,
    0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1,
    0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01,
    0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001
  ]);

  var fixedLitCodeTab = [new Uint32Array([
    0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0,
    0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0,
    0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0,
    0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0,
    0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8,
    0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8,
    0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8,
    0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8,
    0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4,
    0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4,
    0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4,
    0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4,
    0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc,
    0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec,
    0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc,
    0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc,
    0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2,
    0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2,
    0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2,
    0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2,
    0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca,
    0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea,
    0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da,
    0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa,
    0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6,
    0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6,
    0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6,
    0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6,
    0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce,
    0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee,
    0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de,
    0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe,
    0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1,
    0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1,
    0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1,
    0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1,
    0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9,
    0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9,
    0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9,
    0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9,
    0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5,
    0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5,
    0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5,
    0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5,
    0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd,
    0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed,
    0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd,
    0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd,
    0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3,
    0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3,
    0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3,
    0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3,
    0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb,
    0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb,
    0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db,
    0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb,
    0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7,
    0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7,
    0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7,
    0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7,
    0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf,
    0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef,
    0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df,
    0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff
  ]), 9];

  var fixedDistCodeTab = [new Uint32Array([
    0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c,
    0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000,
    0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d,
    0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000
  ]), 5];

  function error(e) {
      throw new Error(e)
  }

  function constructor(bytes) {
    //var bytes = stream.getBytes();
    var bytesPos = 0;

    var cmf = bytes[bytesPos++];
    var flg = bytes[bytesPos++];
    if (cmf == -1 || flg == -1)
      error('Invalid header in flate stream');
    if ((cmf & 0x0f) != 0x08)
      error('Unknown compression method in flate stream');
    if ((((cmf << 8) + flg) % 31) != 0)
      error('Bad FCHECK in flate stream');
    if (flg & 0x20)
      error('FDICT bit set in flate stream');

    this.bytes = bytes;
    this.bytesPos = bytesPos;

    this.codeSize = 0;
    this.codeBuf = 0;

    DecodeStream.call(this);
  }

  constructor.prototype = Object.create(DecodeStream.prototype);

  constructor.prototype.getBits = function(bits) {
    var codeSize = this.codeSize;
    var codeBuf = this.codeBuf;
    var bytes = this.bytes;
    var bytesPos = this.bytesPos;

    var b;
    while (codeSize < bits) {
      if (typeof (b = bytes[bytesPos++]) == 'undefined')
        error('Bad encoding in flate stream');
      codeBuf |= b << codeSize;
      codeSize += 8;
    }
    b = codeBuf & ((1 << bits) - 1);
    this.codeBuf = codeBuf >> bits;
    this.codeSize = codeSize -= bits;
    this.bytesPos = bytesPos;
    return b;
  };

  constructor.prototype.getCode = function(table) {
    var codes = table[0];
    var maxLen = table[1];
    var codeSize = this.codeSize;
    var codeBuf = this.codeBuf;
    var bytes = this.bytes;
    var bytesPos = this.bytesPos;

    while (codeSize < maxLen) {
      var b;
      if (typeof (b = bytes[bytesPos++]) == 'undefined')
        error('Bad encoding in flate stream');
      codeBuf |= (b << codeSize);
      codeSize += 8;
    }
    var code = codes[codeBuf & ((1 << maxLen) - 1)];
    var codeLen = code >> 16;
    var codeVal = code & 0xffff;
    if (codeSize == 0 || codeSize < codeLen || codeLen == 0)
      error('Bad encoding in flate stream');
    this.codeBuf = (codeBuf >> codeLen);
    this.codeSize = (codeSize - codeLen);
    this.bytesPos = bytesPos;
    return codeVal;
  };

  constructor.prototype.generateHuffmanTable = function(lengths) {
    var n = lengths.length;

    // find max code length
    var maxLen = 0;
    for (var i = 0; i < n; ++i) {
      if (lengths[i] > maxLen)
        maxLen = lengths[i];
    }

    // build the table
    var size = 1 << maxLen;
    var codes = new Uint32Array(size);
    for (var len = 1, code = 0, skip = 2;
         len <= maxLen;
         ++len, code <<= 1, skip <<= 1) {
      for (var val = 0; val < n; ++val) {
        if (lengths[val] == len) {
          // bit-reverse the code
          var code2 = 0;
          var t = code;
          for (var i = 0; i < len; ++i) {
            code2 = (code2 << 1) | (t & 1);
            t >>= 1;
          }

          // fill the table entries
          for (var i = code2; i < size; i += skip)
            codes[i] = (len << 16) | val;

          ++code;
        }
      }
    }

    return [codes, maxLen];
  };

  constructor.prototype.readBlock = function() {
    function repeat(stream, array, len, offset, what) {
      var repeat = stream.getBits(len) + offset;
      while (repeat-- > 0)
        array[i++] = what;
    }

    // read block header
    var hdr = this.getBits(3);
    if (hdr & 1)
      this.eof = true;
    hdr >>= 1;

    if (hdr == 0) { // uncompressed block
      var bytes = this.bytes;
      var bytesPos = this.bytesPos;
      var b;

      if (typeof (b = bytes[bytesPos++]) == 'undefined')
        error('Bad block header in flate stream');
      var blockLen = b;
      if (typeof (b = bytes[bytesPos++]) == 'undefined')
        error('Bad block header in flate stream');
      blockLen |= (b << 8);
      if (typeof (b = bytes[bytesPos++]) == 'undefined')
        error('Bad block header in flate stream');
      var check = b;
      if (typeof (b = bytes[bytesPos++]) == 'undefined')
        error('Bad block header in flate stream');
      check |= (b << 8);
      if (check != (~blockLen & 0xffff))
        error('Bad uncompressed block length in flate stream');

      this.codeBuf = 0;
      this.codeSize = 0;

      var bufferLength = this.bufferLength;
      var buffer = this.ensureBuffer(bufferLength + blockLen);
      var end = bufferLength + blockLen;
      this.bufferLength = end;
      for (var n = bufferLength; n < end; ++n) {
        if (typeof (b = bytes[bytesPos++]) == 'undefined') {
          this.eof = true;
          break;
        }
        buffer[n] = b;
      }
      this.bytesPos = bytesPos;
      return;
    }

    var litCodeTable;
    var distCodeTable;
    if (hdr == 1) { // compressed block, fixed codes
      litCodeTable = fixedLitCodeTab;
      distCodeTable = fixedDistCodeTab;
    } else if (hdr == 2) { // compressed block, dynamic codes
      var numLitCodes = this.getBits(5) + 257;
      var numDistCodes = this.getBits(5) + 1;
      var numCodeLenCodes = this.getBits(4) + 4;

      // build the code lengths code table
      var codeLenCodeLengths = Array(codeLenCodeMap.length);
      var i = 0;
      while (i < numCodeLenCodes)
        codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3);
      var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);

      // build the literal and distance code tables
      var len = 0;
      var i = 0;
      var codes = numLitCodes + numDistCodes;
      var codeLengths = new Array(codes);
      while (i < codes) {
        var code = this.getCode(codeLenCodeTab);
        if (code == 16) {
          repeat(this, codeLengths, 2, 3, len);
        } else if (code == 17) {
          repeat(this, codeLengths, 3, 3, len = 0);
        } else if (code == 18) {
          repeat(this, codeLengths, 7, 11, len = 0);
        } else {
          codeLengths[i++] = len = code;
        }
      }

      litCodeTable =
        this.generateHuffmanTable(codeLengths.slice(0, numLitCodes));
      distCodeTable =
        this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes));
    } else {
      error('Unknown block type in flate stream');
    }

    var buffer = this.buffer;
    var limit = buffer ? buffer.length : 0;
    var pos = this.bufferLength;
    while (true) {
      var code1 = this.getCode(litCodeTable);
      if (code1 < 256) {
        if (pos + 1 >= limit) {
          buffer = this.ensureBuffer(pos + 1);
          limit = buffer.length;
        }
        buffer[pos++] = code1;
        continue;
      }
      if (code1 == 256) {
        this.bufferLength = pos;
        return;
      }
      code1 -= 257;
      code1 = lengthDecode[code1];
      var code2 = code1 >> 16;
      if (code2 > 0)
        code2 = this.getBits(code2);
      var len = (code1 & 0xffff) + code2;
      code1 = this.getCode(distCodeTable);
      code1 = distDecode[code1];
      code2 = code1 >> 16;
      if (code2 > 0)
        code2 = this.getBits(code2);
      var dist = (code1 & 0xffff) + code2;
      if (pos + len >= limit) {
        buffer = this.ensureBuffer(pos + len);
        limit = buffer.length;
      }
      for (var k = 0; k < len; ++k, ++pos)
        buffer[pos] = buffer[pos - dist];
    }
  };

  return constructor;
})();
</script>

<script type = "text/javascript">
// Generated by CoffeeScript 1.4.0

/*
# MIT LICENSE
# Copyright (c) 2011 Devon Govett
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
# to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or
# substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/


(function() {
  var PNG;

  PNG = (function() {
    var APNG_BLEND_OP_OVER, APNG_BLEND_OP_SOURCE, APNG_DISPOSE_OP_BACKGROUND, APNG_DISPOSE_OP_NONE, APNG_DISPOSE_OP_PREVIOUS, makeImage, scratchCanvas, scratchCtx;

    PNG.load = function(url, canvas, callback) {
      var xhr,
        _this = this;
      if (typeof canvas === 'function') {
        callback = canvas;
      }
      xhr = new XMLHttpRequest;
      xhr.open("GET", url, true);
      xhr.responseType = "arraybuffer";
      xhr.onload = function() {
        var data, png;
        data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
        png = new PNG(data);
        if (typeof (canvas != null ? canvas.getContext : void 0) === 'function') {
          png.render(canvas);
        }
        return typeof callback === "function" ? callback(png) : void 0;
      };
      return xhr.send(null);
    };

    APNG_DISPOSE_OP_NONE = 0;

    APNG_DISPOSE_OP_BACKGROUND = 1;

    APNG_DISPOSE_OP_PREVIOUS = 2;

    APNG_BLEND_OP_SOURCE = 0;

    APNG_BLEND_OP_OVER = 1;

    function PNG(data) {
      var chunkSize, colors, delayDen, delayNum, frame, i, index, key, section, short, text, _i, _j, _ref;
      this.data = data;
      this.pos = 8;
      this.palette = [];
      this.imgData = [];
      this.transparency = {};
      this.animation = null;
      this.text = {};
      frame = null;
      while (true) {
        chunkSize = this.readUInt32();
        section = ((function() {
          var _i, _results;
          _results = [];
          for (i = _i = 0; _i < 4; i = ++_i) {
            _results.push(String.fromCharCode(this.data[this.pos++]));
          }
          return _results;
        }).call(this)).join('');
        switch (section) {
          case 'IHDR':
            this.width = this.readUInt32();
            this.height = this.readUInt32();
            this.bits = this.data[this.pos++];
            this.colorType = this.data[this.pos++];
            this.compressionMethod = this.data[this.pos++];
            this.filterMethod = this.data[this.pos++];
            this.interlaceMethod = this.data[this.pos++];
            break;
          case 'acTL':
            this.animation = {
              numFrames: this.readUInt32(),
              numPlays: this.readUInt32() || Infinity,
              frames: []
            };
            break;
          case 'PLTE':
            this.palette = this.read(chunkSize);
            break;
          case 'fcTL':
            if (frame) {
              this.animation.frames.push(frame);
            }
            this.pos += 4;
            frame = {
              width: this.readUInt32(),
              height: this.readUInt32(),
              xOffset: this.readUInt32(),
              yOffset: this.readUInt32()
            };
            delayNum = this.readUInt16();
            delayDen = this.readUInt16() || 100;
            frame.delay = 1000 * delayNum / delayDen;
            frame.disposeOp = this.data[this.pos++];
            frame.blendOp = this.data[this.pos++];
            frame.data = [];
            break;
          case 'IDAT':
          case 'fdAT':
            if (section === 'fdAT') {
              this.pos += 4;
              chunkSize -= 4;
            }
            data = (frame != null ? frame.data : void 0) || this.imgData;
            for (i = _i = 0; 0 <= chunkSize ? _i < chunkSize : _i > chunkSize; i = 0 <= chunkSize ? ++_i : --_i) {
              data.push(this.data[this.pos++]);
            }
            break;
          case 'tRNS':
            this.transparency = {};
            switch (this.colorType) {
              case 3:
                this.transparency.indexed = this.read(chunkSize);
                short = 255 - this.transparency.indexed.length;
                if (short > 0) {
                  for (i = _j = 0; 0 <= short ? _j < short : _j > short; i = 0 <= short ? ++_j : --_j) {
                    this.transparency.indexed.push(255);
                  }
                }
                break;
              case 0:
                this.transparency.grayscale = this.read(chunkSize)[0];
                break;
              case 2:
                this.transparency.rgb = this.read(chunkSize);
            }
            break;
          case 'tEXt':
            text = this.read(chunkSize);
            index = text.indexOf(0);
            key = String.fromCharCode.apply(String, text.slice(0, index));
            this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1));
            break;
          case 'IEND':
            if (frame) {
              this.animation.frames.push(frame);
            }
            this.colors = (function() {
              switch (this.colorType) {
                case 0:
                case 3:
                case 4:
                  return 1;
                case 2:
                case 6:
                  return 3;
              }
            }).call(this);
            this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6;
            colors = this.colors + (this.hasAlphaChannel ? 1 : 0);
            this.pixelBitlength = this.bits * colors;
            this.colorSpace = (function() {
              switch (this.colors) {
                case 1:
                  return 'DeviceGray';
                case 3:
                  return 'DeviceRGB';
              }
            }).call(this);
            this.imgData = new Uint8Array(this.imgData);
            return;
          default:
            this.pos += chunkSize;
        }
        this.pos += 4;
        if (this.pos > this.data.length) {
          throw new Error("Incomplete or corrupt PNG file");
        }
      }
      return;
    }

    PNG.prototype.read = function(bytes) {
      var i, _i, _results;
      _results = [];
      for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) {
        _results.push(this.data[this.pos++]);
      }
      return _results;
    };

    PNG.prototype.readUInt32 = function() {
      var b1, b2, b3, b4;
      b1 = this.data[this.pos++] << 24;
      b2 = this.data[this.pos++] << 16;
      b3 = this.data[this.pos++] << 8;
      b4 = this.data[this.pos++];
      return b1 | b2 | b3 | b4;
    };

    PNG.prototype.readUInt16 = function() {
      var b1, b2;
      b1 = this.data[this.pos++] << 8;
      b2 = this.data[this.pos++];
      return b1 | b2;
    };

    PNG.prototype.decodePixels = function(data) {
      var byte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m;
      if (data == null) {
        data = this.imgData;
      }
      if (data.length === 0) {
        return new Uint8Array(0);
      }
      data = new FlateStream(data);
      data = data.getBytes();
      pixelBytes = this.pixelBitlength / 8;
      scanlineLength = pixelBytes * this.width;
      pixels = new Uint8Array(scanlineLength * this.height);
      length = data.length;
      row = 0;
      pos = 0;
      c = 0;
      while (pos < length) {
        switch (data[pos++]) {
          case 0:
            for (i = _i = 0; _i < scanlineLength; i = _i += 1) {
              pixels[c++] = data[pos++];
            }
            break;
          case 1:
            for (i = _j = 0; _j < scanlineLength; i = _j += 1) {
              byte = data[pos++];
              left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
              pixels[c++] = (byte + left) % 256;
            }
            break;
          case 2:
            for (i = _k = 0; _k < scanlineLength; i = _k += 1) {
              byte = data[pos++];
              col = (i - (i % pixelBytes)) / pixelBytes;
              upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
              pixels[c++] = (upper + byte) % 256;
            }
            break;
          case 3:
            for (i = _l = 0; _l < scanlineLength; i = _l += 1) {
              byte = data[pos++];
              col = (i - (i % pixelBytes)) / pixelBytes;
              left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
              upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
              pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256;
            }
            break;
          case 4:
            for (i = _m = 0; _m < scanlineLength; i = _m += 1) {
              byte = data[pos++];
              col = (i - (i % pixelBytes)) / pixelBytes;
              left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
              if (row === 0) {
                upper = upperLeft = 0;
              } else {
                upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)];
              }
              p = left + upper - upperLeft;
              pa = Math.abs(p - left);
              pb = Math.abs(p - upper);
              pc = Math.abs(p - upperLeft);
              if (pa <= pb && pa <= pc) {
                paeth = left;
              } else if (pb <= pc) {
                paeth = upper;
              } else {
                paeth = upperLeft;
              }
              pixels[c++] = (byte + paeth) % 256;
            }
            break;
          default:
            throw new Error("Invalid filter algorithm: " + data[pos - 1]);
        }
        row++;
      }
      return pixels;
    };

    PNG.prototype.decodePalette = function() {
      var c, i, length, palette, pos, ret, transparency, _i, _ref, _ref1;
      palette = this.palette;
      transparency = this.transparency.indexed || [];
      ret = new Uint8Array((transparency.length || 0) + palette.length);
      pos = 0;
      length = palette.length;
      c = 0;
      for (i = _i = 0, _ref = palette.length; _i < _ref; i = _i += 3) {
        ret[pos++] = palette[i];
        ret[pos++] = palette[i + 1];
        ret[pos++] = palette[i + 2];
        ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255;
      }
      return ret;
    };

    PNG.prototype.copyToImageData = function(imageData, pixels) {
      var alpha, colors, data, i, input, j, k, length, palette, v, _ref;
      colors = this.colors;
      palette = null;
      alpha = this.hasAlphaChannel;
      if (this.palette.length) {
        palette = (_ref = this._decodedPalette) != null ? _ref : this._decodedPalette = this.decodePalette();
        colors = 4;
        alpha = true;
      }
      data = imageData.data || imageData;
      length = data.length;
      input = palette || pixels;
      i = j = 0;
      if (colors === 1) {
        while (i < length) {
          k = palette ? pixels[i / 4] * 4 : j;
          v = input[k++];
          data[i++] = v;
          data[i++] = v;
          data[i++] = v;
          data[i++] = alpha ? input[k++] : 255;
          j = k;
        }
      } else {
        while (i < length) {
          k = palette ? pixels[i / 4] * 4 : j;
          data[i++] = input[k++];
          data[i++] = input[k++];
          data[i++] = input[k++];
          data[i++] = alpha ? input[k++] : 255;
          j = k;
        }
      }
    };

    PNG.prototype.decode = function() {
      var ret;
      ret = new Uint8Array(this.width * this.height * 4);
      this.copyToImageData(ret, this.decodePixels());
      return ret;
    };

    scratchCanvas = document.createElement('canvas');

    scratchCtx = scratchCanvas.getContext('2d');

    makeImage = function(imageData) {
      var img;
      scratchCtx.width = imageData.width;
      scratchCtx.height = imageData.height;
      scratchCtx.clearRect(0, 0, imageData.width, imageData.height);
      scratchCtx.putImageData(imageData, 0, 0);
      img = new Image;
      img.src = scratchCanvas.toDataURL();
      return img;
    };

    PNG.prototype.decodeFrames = function(ctx) {
      var frame, i, imageData, pixels, _i, _len, _ref, _results;
      if (!this.animation) {
        return;
      }
      _ref = this.animation.frames;
      _results = [];
      for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
        frame = _ref[i];
        imageData = ctx.createImageData(frame.width, frame.height);
        pixels = this.decodePixels(new Uint8Array(frame.data));
        this.copyToImageData(imageData, pixels);
        frame.imageData = imageData;
        _results.push(frame.image = makeImage(imageData));
      }
      return _results;
    };

    PNG.prototype.renderFrame = function(ctx, number) {
      var frame, frames, prev;
      frames = this.animation.frames;
      frame = frames[number];
      prev = frames[number - 1];
      if (number === 0) {
        ctx.clearRect(0, 0, this.width, this.height);
      }
      if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_BACKGROUND) {
        ctx.clearRect(prev.xOffset, prev.yOffset, prev.width, prev.height);
      } else if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_PREVIOUS) {
        ctx.putImageData(prev.imageData, prev.xOffset, prev.yOffset);
      }
      if (frame.blendOp === APNG_BLEND_OP_SOURCE) {
        ctx.clearRect(frame.xOffset, frame.yOffset, frame.width, frame.height);
      }
      return ctx.drawImage(frame.image, frame.xOffset, frame.yOffset);
    };

    PNG.prototype.animate = function(ctx) {
      var doFrame, frameNumber, frames, numFrames, numPlays, _ref,
        _this = this;
      frameNumber = 0;
      _ref = this.animation, numFrames = _ref.numFrames, frames = _ref.frames, numPlays = _ref.numPlays;
      return (doFrame = function() {
        var f, frame;
        f = frameNumber++ % numFrames;
        frame = frames[f];
        _this.renderFrame(ctx, f);
        if (numFrames > 1 && frameNumber / numFrames < numPlays) {
          return _this.animation._timeout = setTimeout(doFrame, frame.delay);
        }
      })();
    };

    PNG.prototype.stopAnimation = function() {
      var _ref;
      return clearTimeout((_ref = this.animation) != null ? _ref._timeout : void 0);
    };

    PNG.prototype.render = function(canvas) {
      var ctx, data;
      if (canvas._png) {
        canvas._png.stopAnimation();
      }
      canvas._png = this;
      canvas.width = this.width;
      canvas.height = this.height;
      ctx = canvas.getContext("2d");
      if (this.animation) {
        this.decodeFrames(ctx);
        return this.animate(ctx);
      } else {
        data = ctx.createImageData(this.width, this.height);
        this.copyToImageData(data, this.decodePixels());
        return ctx.putImageData(data, 0, 0);
      }
    };

    return PNG;

  })();

  window.PNG = PNG;

}).call(this);
</script>

<script>
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(s,p){var m={},l=m.lib={},n=function(){},r=l.Base={extend:function(b){n.prototype=this;var h=new n;b&&h.mixIn(b);h.hasOwnProperty("init")||(h.init=function(){h.$super.init.apply(this,arguments)});h.init.prototype=h;h.$super=this;return h},create:function(){var b=this.extend();b.init.apply(b,arguments);return b},init:function(){},mixIn:function(b){for(var h in b)b.hasOwnProperty(h)&&(this[h]=b[h]);b.hasOwnProperty("toString")&&(this.toString=b.toString)},clone:function(){return this.init.prototype.extend(this)}},
q=l.WordArray=r.extend({init:function(b,h){b=this.words=b||[];this.sigBytes=h!=p?h:4*b.length},toString:function(b){return(b||t).stringify(this)},concat:function(b){var h=this.words,a=b.words,j=this.sigBytes;b=b.sigBytes;this.clamp();if(j%4)for(var g=0;g<b;g++)h[j+g>>>2]|=(a[g>>>2]>>>24-8*(g%4)&255)<<24-8*((j+g)%4);else if(65535<a.length)for(g=0;g<b;g+=4)h[j+g>>>2]=a[g>>>2];else h.push.apply(h,a);this.sigBytes+=b;return this},clamp:function(){var b=this.words,h=this.sigBytes;b[h>>>2]&=4294967295<<
32-8*(h%4);b.length=s.ceil(h/4)},clone:function(){var b=r.clone.call(this);b.words=this.words.slice(0);return b},random:function(b){for(var h=[],a=0;a<b;a+=4)h.push(4294967296*s.random()|0);return new q.init(h,b)}}),v=m.enc={},t=v.Hex={stringify:function(b){var a=b.words;b=b.sigBytes;for(var g=[],j=0;j<b;j++){var k=a[j>>>2]>>>24-8*(j%4)&255;g.push((k>>>4).toString(16));g.push((k&15).toString(16))}return g.join("")},parse:function(b){for(var a=b.length,g=[],j=0;j<a;j+=2)g[j>>>3]|=parseInt(b.substr(j,
2),16)<<24-4*(j%8);return new q.init(g,a/2)}},a=v.Latin1={stringify:function(b){var a=b.words;b=b.sigBytes;for(var g=[],j=0;j<b;j++)g.push(String.fromCharCode(a[j>>>2]>>>24-8*(j%4)&255));return g.join("")},parse:function(b){for(var a=b.length,g=[],j=0;j<a;j++)g[j>>>2]|=(b.charCodeAt(j)&255)<<24-8*(j%4);return new q.init(g,a)}},u=v.Utf8={stringify:function(b){try{return decodeURIComponent(escape(a.stringify(b)))}catch(g){throw Error("Malformed UTF-8 data");}},parse:function(b){return a.parse(unescape(encodeURIComponent(b)))}},
g=l.BufferedBlockAlgorithm=r.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(b){"string"==typeof b&&(b=u.parse(b));this._data.concat(b);this._nDataBytes+=b.sigBytes},_process:function(b){var a=this._data,g=a.words,j=a.sigBytes,k=this.blockSize,m=j/(4*k),m=b?s.ceil(m):s.max((m|0)-this._minBufferSize,0);b=m*k;j=s.min(4*b,j);if(b){for(var l=0;l<b;l+=k)this._doProcessBlock(g,l);l=g.splice(0,b);a.sigBytes-=j}return new q.init(l,j)},clone:function(){var b=r.clone.call(this);
b._data=this._data.clone();return b},_minBufferSize:0});l.Hasher=g.extend({cfg:r.extend(),init:function(b){this.cfg=this.cfg.extend(b);this.reset()},reset:function(){g.reset.call(this);this._doReset()},update:function(b){this._append(b);this._process();return this},finalize:function(b){b&&this._append(b);return this._doFinalize()},blockSize:16,_createHelper:function(b){return function(a,g){return(new b.init(g)).finalize(a)}},_createHmacHelper:function(b){return function(a,g){return(new k.HMAC.init(b,
g)).finalize(a)}}});var k=m.algo={};return m}(Math);
(function(s){function p(a,k,b,h,l,j,m){a=a+(k&b|~k&h)+l+m;return(a<<j|a>>>32-j)+k}function m(a,k,b,h,l,j,m){a=a+(k&h|b&~h)+l+m;return(a<<j|a>>>32-j)+k}function l(a,k,b,h,l,j,m){a=a+(k^b^h)+l+m;return(a<<j|a>>>32-j)+k}function n(a,k,b,h,l,j,m){a=a+(b^(k|~h))+l+m;return(a<<j|a>>>32-j)+k}for(var r=CryptoJS,q=r.lib,v=q.WordArray,t=q.Hasher,q=r.algo,a=[],u=0;64>u;u++)a[u]=4294967296*s.abs(s.sin(u+1))|0;q=q.MD5=t.extend({_doReset:function(){this._hash=new v.init([1732584193,4023233417,2562383102,271733878])},
_doProcessBlock:function(g,k){for(var b=0;16>b;b++){var h=k+b,w=g[h];g[h]=(w<<8|w>>>24)&16711935|(w<<24|w>>>8)&4278255360}var b=this._hash.words,h=g[k+0],w=g[k+1],j=g[k+2],q=g[k+3],r=g[k+4],s=g[k+5],t=g[k+6],u=g[k+7],v=g[k+8],x=g[k+9],y=g[k+10],z=g[k+11],A=g[k+12],B=g[k+13],C=g[k+14],D=g[k+15],c=b[0],d=b[1],e=b[2],f=b[3],c=p(c,d,e,f,h,7,a[0]),f=p(f,c,d,e,w,12,a[1]),e=p(e,f,c,d,j,17,a[2]),d=p(d,e,f,c,q,22,a[3]),c=p(c,d,e,f,r,7,a[4]),f=p(f,c,d,e,s,12,a[5]),e=p(e,f,c,d,t,17,a[6]),d=p(d,e,f,c,u,22,a[7]),
c=p(c,d,e,f,v,7,a[8]),f=p(f,c,d,e,x,12,a[9]),e=p(e,f,c,d,y,17,a[10]),d=p(d,e,f,c,z,22,a[11]),c=p(c,d,e,f,A,7,a[12]),f=p(f,c,d,e,B,12,a[13]),e=p(e,f,c,d,C,17,a[14]),d=p(d,e,f,c,D,22,a[15]),c=m(c,d,e,f,w,5,a[16]),f=m(f,c,d,e,t,9,a[17]),e=m(e,f,c,d,z,14,a[18]),d=m(d,e,f,c,h,20,a[19]),c=m(c,d,e,f,s,5,a[20]),f=m(f,c,d,e,y,9,a[21]),e=m(e,f,c,d,D,14,a[22]),d=m(d,e,f,c,r,20,a[23]),c=m(c,d,e,f,x,5,a[24]),f=m(f,c,d,e,C,9,a[25]),e=m(e,f,c,d,q,14,a[26]),d=m(d,e,f,c,v,20,a[27]),c=m(c,d,e,f,B,5,a[28]),f=m(f,c,
d,e,j,9,a[29]),e=m(e,f,c,d,u,14,a[30]),d=m(d,e,f,c,A,20,a[31]),c=l(c,d,e,f,s,4,a[32]),f=l(f,c,d,e,v,11,a[33]),e=l(e,f,c,d,z,16,a[34]),d=l(d,e,f,c,C,23,a[35]),c=l(c,d,e,f,w,4,a[36]),f=l(f,c,d,e,r,11,a[37]),e=l(e,f,c,d,u,16,a[38]),d=l(d,e,f,c,y,23,a[39]),c=l(c,d,e,f,B,4,a[40]),f=l(f,c,d,e,h,11,a[41]),e=l(e,f,c,d,q,16,a[42]),d=l(d,e,f,c,t,23,a[43]),c=l(c,d,e,f,x,4,a[44]),f=l(f,c,d,e,A,11,a[45]),e=l(e,f,c,d,D,16,a[46]),d=l(d,e,f,c,j,23,a[47]),c=n(c,d,e,f,h,6,a[48]),f=n(f,c,d,e,u,10,a[49]),e=n(e,f,c,d,
C,15,a[50]),d=n(d,e,f,c,s,21,a[51]),c=n(c,d,e,f,A,6,a[52]),f=n(f,c,d,e,q,10,a[53]),e=n(e,f,c,d,y,15,a[54]),d=n(d,e,f,c,w,21,a[55]),c=n(c,d,e,f,v,6,a[56]),f=n(f,c,d,e,D,10,a[57]),e=n(e,f,c,d,t,15,a[58]),d=n(d,e,f,c,B,21,a[59]),c=n(c,d,e,f,r,6,a[60]),f=n(f,c,d,e,z,10,a[61]),e=n(e,f,c,d,j,15,a[62]),d=n(d,e,f,c,x,21,a[63]);b[0]=b[0]+c|0;b[1]=b[1]+d|0;b[2]=b[2]+e|0;b[3]=b[3]+f|0},_doFinalize:function(){var a=this._data,k=a.words,b=8*this._nDataBytes,h=8*a.sigBytes;k[h>>>5]|=128<<24-h%32;var l=s.floor(b/
4294967296);k[(h+64>>>9<<4)+15]=(l<<8|l>>>24)&16711935|(l<<24|l>>>8)&4278255360;k[(h+64>>>9<<4)+14]=(b<<8|b>>>24)&16711935|(b<<24|b>>>8)&4278255360;a.sigBytes=4*(k.length+1);this._process();a=this._hash;k=a.words;for(b=0;4>b;b++)h=k[b],k[b]=(h<<8|h>>>24)&16711935|(h<<24|h>>>8)&4278255360;return a},clone:function(){var a=t.clone.call(this);a._hash=this._hash.clone();return a}});r.MD5=t._createHelper(q);r.HmacMD5=t._createHmacHelper(q)})(Math);
</script>

<script>
/*
CryptoJS v3.1
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
(function () {
    // Check if typed arrays are supported
    if (typeof ArrayBuffer != 'function') {
        return;
    }

    // Shortcuts
    var C = CryptoJS;
    var C_lib = C.lib;
    var WordArray = C_lib.WordArray;

    // Reference original init
    var $superInit = WordArray.init;

    // Augment WordArray.init to handle typed arrays
    WordArray.init = function (typedArray) {
        // Convert buffers to data view
        if (typedArray instanceof ArrayBuffer) {
            typedArray = new DataView(typedArray);
        }

        // Convert array views to data view
        if (
            typedArray instanceof Int8Array ||
            typedArray instanceof Uint8Array ||
            typedArray instanceof Uint8ClampedArray ||
            typedArray instanceof Int16Array ||
            typedArray instanceof Uint16Array ||
            typedArray instanceof Int32Array ||
            typedArray instanceof Uint32Array ||
            typedArray instanceof Float32Array ||
            typedArray instanceof Float64Array
        ) {
            typedArray = new DataView(typedArray.buffer);
        }

        // Handle data views
        if (typedArray instanceof DataView) {
            var typedArrayByteLength = typedArray.byteLength;

            var words = [];
            for (var i = 0; i < typedArrayByteLength; i++) {
                words[i >>> 2] |= typedArray.getUint8(i) << (24 - (i % 4) * 8);
            }

            $superInit.call(this, words, typedArrayByteLength);
        } else {
            // Else call normal init
            $superInit.apply(this, arguments);
        }
    };

    WordArray.init.prototype = WordArray;
}());
</script>



<script type="text/javascript">


var canvas;

function PlatformJs()
{
}

PlatformJs.prototype = new GamePlatform();

PlatformJs.prototype.addOnCrash = function(handler) {
}

var keyEventHandler;
PlatformJs.prototype.addOnKeyEvent = function(handler) {
	keyEventHandler = handler;
}

var mouseEventHandler;
PlatformJs.prototype.addOnMouseEvent = function(handler) {
	mouseEventHandler = handler;
}

var newFrameHandler;
PlatformJs.prototype.addOnNewFrame = function(handler) {
	newFrameHandler = handler;
}

var touchEventHandler;
PlatformJs.prototype.addOnTouchEvent = function(handler) {
	touchEventHandler = handler;
}

PlatformJs.prototype.applicationDoEvents = function() {
}

// Audio
var audioContext;

PlatformJs.prototype.audioDataCreate = function(data, dataLength) {
	var sound = {};
	sound.loaded = false;
	var arr = new ArrayBuffer(dataLength);
	var arr8 = new Uint8Array(arr);
	for(var i=0;i<dataLength;i++)
	{
		arr8[i]=data[i];
	}
	audioContext.decodeAudioData(arr, function(buffer) {
		sound.buffer = buffer;
		sound.loaded = true;
	});
	return sound;
}

PlatformJs.prototype.audioDataLoaded = function(data) {
	return data.loaded;
}

PlatformJs.prototype.audioCreate = function(data) {
	var audio = {};
	audio.data = data;
	audio.pausedAt = null;
	return audio;
}

PlatformJs.prototype.audioPlay = function(audio) {
	audio.src = audioContext.createBufferSource();
	audio.src.buffer = audio.data.buffer;
	audio.src.connect(audioContext.destination);
	if (audio.pausedAt)
	{
		audio.startedAt = Date.now() - audio.pausedAt;
		audio.src.start(0, audio.pausedAt / 1000);
		audio.pausedAt = null;
	}
	else
	{
		audio.startedAt = Date.now();
		audio.src.start(0);
	}
}

PlatformJs.prototype.audioPause = function(audio) {
	audio.src.stop();
	audio.pausedAt = Date.now() - audio.startedAt;
}

PlatformJs.prototype.audioDelete = function(audio) {
	audio.src.stop();
}

PlatformJs.prototype.audioFinished = function(audio) {
	return (Date.now() - audio.startedAt) > audio.data.buffer.duration * 1000;
}

PlatformJs.prototype.audioSetPosition = function(audio, x, y, z) {
}

PlatformJs.prototype.audioUpdateListener = function(posX, posY, posZ, orientX, orientY, orientZ) {
}

PlatformJs.prototype.aviWriterCreate = function() {
	return null;
}

PlatformJs.prototype.bindTexture2d = function(texture) {
	try
	{
		gl.bindTexture(gl.TEXTURE_2D, loadedTextures[texture]);
	}
	catch(ex)
	{
		console.log(ex);
	}
}

var textureCanvas;
var textureCanvasContext;

PlatformJs.prototype.bitmapCreate = function(width, height) {
	var c = document.createElement('canvas');
	c.width = width;
	c.height = height;
	c.loaded = true;
	return c;
}

    var BASE64_CHUNK_SIZE = 100;
    var toBase64 = function(data) {
      var str = "";
      for (var i = 0; i < data.length; i += BASE64_CHUNK_SIZE) {
        str += String.fromCharCode.apply(String, data.subarray(i, i + BASE64_CHUNK_SIZE));
      }
      return btoa(str);
    };

PlatformJs.prototype.bitmapCreateFromPng = function(data, dataLength) {
	var c = document.createElement('canvas');
	if (!data)
	{
		return c;
	}
	// "data:image/png;base64" is buggy in Firefox.
	// Use custom png decoder instead.
	try
	{
		var png = new PNG(data);
		png.render(c);
	}
	catch (ex)
	{
		console.log(ex);
	}
	c.loaded = true;
	return c;
}

PlatformJs.prototype.bitmapDelete = function(bmp) {
}

PlatformJs.prototype.bitmapGetHeight = function(bmp) {
	if(bmp==null){return 100;}
	return bmp.height;
}

var colorA = function(color) {
	var a = Game.intToByte(color >> 24);
	return a;
}

var colorB = function(color) {
	var b = Game.intToByte(color);
	return b;
}

var colorG = function(color) {
	var g = Game.intToByte(color >> 8);
	return g;
}

var colorR = function(color) {
	var r = Game.intToByte(color >> 16);
	return r;
}

var colorFromArgb = function(a, r, g, b) {
	var iCol = a << 24 | r << 16 | g << 8 | b;
	return iCol;
}

PlatformJs.prototype.bitmapGetPixelsArgb = function(bitmap, bmpPixels) {
	var imageData = bitmap.getContext("2d").getImageData(0, 0, bitmap.width, bitmap.height);
	var width = bitmap.width;
	var height = bitmap.height;

	for(var x=0;x<width;x++)
	{
		for(var y=0;y<height;y++)
		{
			var pos = y*width + x;
			var r = imageData.data[pos * 4 + 0];
			var g = imageData.data[pos * 4 + 1];
			var b = imageData.data[pos * 4 + 2];
			var a = imageData.data[pos * 4 + 3];
			bmpPixels[pos] = colorFromArgb(a,r,g,b);
		}
	}
	bitmap.getContext("2d").putImageData(imageData, 0, 0);
}

PlatformJs.prototype.bitmapGetWidth = function(bmp) {
	if(bmp==null){return 100;}
	return bmp.width;
}

PlatformJs.prototype.bitmapSetPixelsArgb = function(bmp, pixels) {
	var imageData = bmp.getContext("2d").createImageData(bmp.width, bmp.height);
	var arr = imageData.data;
	var width = bmp.width;
	var height = bmp.height;
	for (var x = 0; x < width; x++)
	{
		for (var y = 0; y < height; y++)
		{
			var pos = y*width + x;
			var color = pixels[pos];
			var r = colorR(color);
			var g = colorG(color);
			var b = colorB(color);
			var a = colorA(color);
			arr[pos * 4 + 0] = r;
			arr[pos * 4 + 1] = g;
			arr[pos * 4 + 2] = b;
			arr[pos * 4 + 3] = a;
		}
	}
	bmp.getContext("2d").putImageData(imageData,0,0);
}

PlatformJs.prototype.byteArrayLength = function(arr) {
	return arr.length;
}

PlatformJs.prototype.castToPlayerInterpolationState = function(a) {
	return a;
}

PlatformJs.prototype.changeResolution = function(width, height, bitsPerPixel, refreshRate) {
}

PlatformJs.prototype.charArrayToString = function(charArray, length) {
	var arr = [];
	for(var i = 0; i < length; i++)
	{
		arr[i] = String.fromCharCode(charArray[i]);
	}
	return arr.join("");
}

PlatformJs.prototype.chatLog = function(servername, p) {
	return false;
}

PlatformJs.prototype.clipboardContainsText = function() {
	return false;
}

PlatformJs.prototype.clipboardGetText = function() {
	return null;
}

PlatformJs.prototype.clipboardSetText = function(s) {
}

PlatformJs.prototype.consoleWriteLine = function(p) {
}

PlatformJs.prototype.createModel = function(modelData) {
	model = {};

        model.cubeVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, model.cubeVertexPositionBuffer);
        var vertices = modelData.xyz;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        model.cubeVertexPositionBuffer.itemSize = 3;
        model.cubeVertexPositionBuffer.numItems = modelData.verticesCount;

        model.cubeVertexTextureCoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, model.cubeVertexTextureCoordBuffer);
        var textureCoords = modelData.uv;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
        model.cubeVertexTextureCoordBuffer.itemSize = 2;
        model.cubeVertexTextureCoordBuffer.numItems = modelData.verticesCount;

        model.cubeVertexColorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, model.cubeVertexColorBuffer);
        var colors2 = new Float32Array(modelData.verticesCount * 4);
        var colors = modelData.rgba;
        if(!colors)
        {
          for(var i=0;i<colors2.length;i++)
          {
            colors2[i] = 1;
          }
        }
        else
        {
          for(var i=0;i<colors.length;i++)
          {
            colors2[i] = colors[i]/255;
          }
        }
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors2), gl.STATIC_DRAW);
        model.cubeVertexColorBuffer.itemSize = 4;
        model.cubeVertexColorBuffer.numItems = modelData.verticesCount;

        model.cubeVertexIndexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.cubeVertexIndexBuffer);
        var cubeVertexIndices = modelData.indices;
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
        model.cubeVertexIndexBuffer.itemSize = 1;
        model.cubeVertexIndexBuffer.numItems = modelData.indicesCount;

        	if (modelData.getMode() == DrawModeEnum.LINES)
	{
		model.mode = gl.LINES;
	}
	else
	{
		model.mode = gl.TRIANGLES;
	}

        return model;
}


function getPowerOfTwo(value, pow) {
	var pow = pow || 1;
	while(pow<value) {
		pow *= 2;
	}
	return pow;
}

function setFont(ctx, text, fontSize, color)
{
	ctx.fillStyle ='rgb('+colorR(color)+','+colorG(color)+','+colorB(color)+')';
	ctx.lineWidth = 3.5;
	ctx.strokeStyle = 'black';
	ctx.font = fontSize+"px Verdana";
	ctx.textAlign = '';
	ctx.textBaseline = 'top';
}

function cloneCanvas(oldCanvas) {

    //create a new canvas
    var newCanvas = document.createElement('canvas');
    var context = newCanvas.getContext('2d');

    //set dimensions
    newCanvas.width = oldCanvas.width;
    newCanvas.height = oldCanvas.height;

    //apply the old canvas to the new one
    context.drawImage(oldCanvas, 0, 0);

    //return the new canvas
    return newCanvas;
}

PlatformJs.prototype.createTextTexture = function(t) {
	textureCanvas.width = getPowerOfTwo(textureCanvasContext.measureText(t.text).width);
	textureCanvas.height = getPowerOfTwo(2 * t.font.size);

	setFont(textureCanvasContext, t.text, t.font.size, t.color);
	textureCanvasContext.fillText(t.text, 0, 0);

	return cloneCanvas(textureCanvas);
}

PlatformJs.prototype.deleteModel = function(model) {
}

PlatformJs.prototype.directoryGetFiles = function(path, length) {
	return null;
}

PlatformJs.prototype.drawModel = function(model) {
        gl.bindBuffer(gl.ARRAY_BUFFER, model.cubeVertexPositionBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, model.cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, model.cubeVertexTextureCoordBuffer);
        gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, model.cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, model.cubeVertexColorBuffer);
        gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, model.cubeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);


        gl.activeTexture(gl.TEXTURE0);
        //gl.bindTexture(gl.TEXTURE_2D, crateTextures[filter]);
        gl.uniform1i(shaderProgram.samplerUniform, 0);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.cubeVertexIndexBuffer);
        //setMatrixUniforms();
        gl.drawElements(model.mode, model.cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
}

PlatformJs.prototype.drawModelData = function(data) {
	var model = this.createModel(data);
	this.drawModel(model);
	this.deleteModel(model);
}

PlatformJs.prototype.drawModels = function(model, count) {
	for(var i = 0; i < count; i++)
	{
		this.drawModel(model[i]);
	}
}

PlatformJs.prototype.enetAvailable = function() {
	return false;
}

PlatformJs.prototype.enetCreateHost = function() {
	return null;
}

PlatformJs.prototype.enetHostCheckEvents = function(host, event_) {
	return false;
}

PlatformJs.prototype.enetHostConnect = function(host, hostName, port, data, channelLimit) {
	return null;
}

PlatformJs.prototype.enetHostInitialize = function(host, address, peerLimit, channelLimit, incomingBandwidth, outgoingBandwidth) {
}

PlatformJs.prototype.enetHostService = function(host, timeout, enetEvent) {
	return false;
}

PlatformJs.prototype.enetPeerSend = function(peer, channelID, data, dataLength, flags) {
}

PlatformJs.prototype.exit = function() {
}

PlatformJs.prototype.exitAvailable = function() {
	return false;
}

document.exitPointerLock = document.exitPointerLock    ||
                           document.mozExitPointerLock ||
                           document.webkitExitPointerLock;
PlatformJs.prototype.exitMousePointerLock = function() {
	document.exitPointerLock();
}

PlatformJs.prototype.fileName = function(fullpath) {
	return null;
}

PlatformJs.prototype.fileOpenDialog = function(extension, extensionName, initialDirectory) {
	return null;
}

PlatformJs.prototype.fileReadAllLines = function(path, length) {
	return null;
}

PlatformJs.prototype.floatModulo = function(a, b) {
	return a % b;
}

PlatformJs.prototype.floatParse = function(value) {
	return parseFloat(value);
}

PlatformJs.prototype.floatToInt = function(value) {
	return value | 0;
}

PlatformJs.prototype.floatToString = function(value) {
	return value.toString();
}

PlatformJs.prototype.floatTryParse = function(s, ret) {
	if (!isNaN(s))
	{
		ret.value = parseFloat(s);
		return true;
	}
	return false;
}

PlatformJs.prototype.focused = function() {
	return true;
}

PlatformJs.prototype.gLDeleteTexture = function(id) {
}

PlatformJs.prototype.gLDisableAlphaTest = function() {
}

PlatformJs.prototype.gLEnableAlphaTest = function() {
}

PlatformJs.prototype.gLLineWidth = function(width) {
}

PlatformJs.prototype.getCanvasHeight = function() {
	return canvas.height;
}

PlatformJs.prototype.getCanvasWidth = function() {
	return canvas.width;
}

PlatformJs.prototype.getDisplayResolutionDefault = function() {
	return null;
}

PlatformJs.prototype.getDisplayResolutions = function(resolutionsCount) {
	return null;
}

PlatformJs.prototype.getGameVersion = function() {
	return null;
}

PlatformJs.prototype.getLanguageIso6391 = function() {
	return "en";
}

PlatformJs.prototype.getPreferences = function() {
	var p = new Preferences();
	var pp = JSON.parse(localStorage.getItem("Preferences"));
	if (pp)
	{
		p.items.items = pp.items;
		p.items.count = pp.count;
	}
	return p;
}

PlatformJs.prototype.getWindowState = function() {
	return WindowState.MAXIMIZED;
}

PlatformJs.prototype.glClearColorBufferAndDepthBuffer = function() {
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}

PlatformJs.prototype.glClearColorRgbaf = function(r, g, b, a) {
        gl.clearColor(r, g, b, a);
}

PlatformJs.prototype.glClearDepthBuffer = function() {
	gl.clear(gl.DEPTH_BUFFER_BIT);
}

PlatformJs.prototype.glColorMaterialFrontAndBackAmbientAndDiffuse = function() {
}

PlatformJs.prototype.glCullFaceBack = function() {
}

PlatformJs.prototype.glDepthMask = function(flag) {
	gl.depthMask(flag);
}

PlatformJs.prototype.glDisableCullFace = function() {
}

PlatformJs.prototype.glDisableDepthTest = function() {
        gl.disable(gl.DEPTH_TEST);
}

PlatformJs.prototype.glDisableFog = function() {
}

PlatformJs.prototype.glEnableColorMaterial = function() {
}

PlatformJs.prototype.glEnableCullFace = function() {
}

PlatformJs.prototype.glEnableDepthTest = function() {
        gl.enable(gl.DEPTH_TEST);
}

PlatformJs.prototype.glEnableFog = function() {
}

PlatformJs.prototype.glEnableLighting = function() {
}

PlatformJs.prototype.glEnableTexture2d = function() {
}

PlatformJs.prototype.glFogFogColor = function(r, g, b, a) {
}

PlatformJs.prototype.glFogFogDensity = function(density) {
}

PlatformJs.prototype.glFogFogModeExp2 = function() {
}

PlatformJs.prototype.glGetMaxTextureSize = function() {
	return 0;
}

PlatformJs.prototype.glHintFogHintNicest = function() {
}

PlatformJs.prototype.glLightModelAmbient = function(r, g, b) {
}

PlatformJs.prototype.glShadeModelSmooth = function() {
}

PlatformJs.prototype.glViewport = function(x, y, width, height) {
	gl.viewport(x, y, width, height);
}

PlatformJs.prototype.grabScreenshot = function() {
	return null;
}

PlatformJs.prototype.gzipCompress = function(data, dataLength, retLength) {
	return null;
}

PlatformJs.prototype.gzipDecompress = function(compressed, compressedLength, ret) {
	var compressed2 = [];
	for(var i=0;i<compressedLength;i++)
	{
		compressed2[i] = compressed[i];
	}

	var gunzip = new Zlib.Gunzip(compressed2);
	var decompressed = gunzip.decompress();

	for(var i=0;i<decompressed.length;i++)
	{
		ret[i] = decompressed[i];
	}
}

PlatformJs.prototype.initShaders = function() {
}

PlatformJs.prototype.intParse = function(value) {
	return parseInt(value);
}

PlatformJs.prototype.intToString = function(value) {
	return value.toString();
}

PlatformJs.prototype.isCached = function(md5) {
	return false;
}

PlatformJs.prototype.isChecksum = function(checksum) {
	return false;
}

PlatformJs.prototype.decodeHTMLEntities = function(htmlencodedstring) {
	var parser = new DOMParser();
	var dom = parser.parseFromString('<body>' + htmlencodedstring, 'text/html');
	return dom.body.textContent;
}

PlatformJs.prototype.isDebuggerAttached = function() {
	return false;
}

PlatformJs.prototype.isFastSystem = function() {
	return false;
}

function _isMousePointerLocked() {
	return document.pointerLockElement != null || document.mozPointerLockElement != null;
}

PlatformJs.prototype.isMousePointerLocked = function() {
  return _isMousePointerLocked();
}

PlatformJs.prototype.isSmallScreen = function() {
	return true;
}

PlatformJs.prototype.isValidTypingChar = function(c) {
	return c != 13 && c != 8;
}

PlatformJs.prototype.keyName = function(key) {
	return null;
}

PlatformJs.prototype.loadAssetFromCache = function(md5) {
	return null;
}

//this function will work cross-browser for loading scripts asynchronously
function loadScript(src, callback)
{
  var s,
      r,
      t;
  r = false;
  s = document.createElement('script');
  s.type = 'text/javascript';
  s.src = src;
  s.onload = s.onreadystatechange = function() {
    //console.log( this.readyState ); //uncomment this line to see which ready states are called.
    if ( !r && (!this.readyState || this.readyState == 'complete') )
    {
      r = true;
      callback();
    }
  };
  t = document.getElementsByTagName('script')[0];
  t.parentNode.insertBefore(s, t);
}

var assets;
PlatformJs.prototype.loadAssetsAsyc = function(list, progress) {

loadScript("Assets.js", function()
{
assets = new Assets();
list.count = assets.count;
list.items = {};
for(var i = 0; i < assets.count; i++)
{
	var item = {};
	item.name = assets.name[i].toLowerCase();
	item.data = assets.data[i];
	item.dataLength = assets.length[i];
	item.md5 = CryptoJS.MD5(CryptoJS.lib.WordArray.create(assets.data[i])).toString(CryptoJS.enc.Hex);
	list.items[i] = item;
}
progress.value = 1;
});
}

var loadedTextures = [];
loadedTextures[0] = null;

PlatformJs.prototype.loadTextureFromBitmap = function(bmp) {
	var texture = gl.createTexture();

	gl.bindTexture(gl.TEXTURE_2D, texture);
	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bmp);
  gl.generateMipmap(gl.TEXTURE_2D);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);

	gl.bindTexture(gl.TEXTURE_2D, null);

	loadedTextures.push(texture);
	return loadedTextures.length - 1;
}

PlatformJs.prototype.mathAcos = function(p) {
	return Math.acos(p);
}

PlatformJs.prototype.mathCos = function(a) {
	return Math.cos(a);
}

PlatformJs.prototype.mathSin = function(a) {
	return Math.sin(a);
}

PlatformJs.prototype.mathSqrt = function(value) {
	return Math.sqrt(value);
}

PlatformJs.prototype.mathTan = function(p) {
	return Math.tan(p);
}

PlatformJs.prototype.messageBoxShowError = function(text, caption) {
	alert(caption + "\n" + text);
}

PlatformJs.prototype.monitorCreate = function() {
	return null;
}

PlatformJs.prototype.monitorEnter = function(monitorObject) {
}

PlatformJs.prototype.monitorExit = function(monitorObject) {
}

PlatformJs.prototype.mouseCursorIsVisible = function() {
	return true;
}

PlatformJs.prototype.mouseCursorSetVisible = function(value) {
}

PlatformJs.prototype.multithreadingAvailable = function() {
	return false;
}

PlatformJs.prototype.openLinkInBrowser = function(url) {
}

PlatformJs.prototype.parseUri = function(uri) {
	return null;
}

PlatformJs.prototype.pathCombine = function(part1, part2) {
	return null;
}

PlatformJs.prototype.pathSavegames = function() {
	return null;
}

PlatformJs.prototype.pathStorage = function() {
	return null;
}

PlatformJs.prototype.queryStringValue = function(key) {
	// http://stackoverflow.com/a/3855394
	var qs = (function(a) {
	    if (a == "") return {};
	    var b = {};
	    for (var i = 0; i < a.length; ++i)
	    {
	        var p=a[i].split('=', 2);
	        if (p.length == 1)
		b[p[0]] = "";
	        else
		b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
	    }
	    return b;
	})(window.location.search.substr(1).split('&'));
	return qs[key];
}

PlatformJs.prototype.queueUserWorkItem = function(action) {
}

PlatformJs.prototype.randomCreate = function() {
	var random = {};
	random.next = function() { return Math.floor(Math.random() * 2147483647); };
	random.maxNext = function(max) { return Math.floor(Math.random() * max); };
	random.nextFloat = function() { return Math.random(); };
	return random;
}

PlatformJs.prototype.readAllLines = function(p, retCount) {
	var lines = p.split("\n");
	retCount.value = lines.length;
	return lines;
}

PlatformJs.prototype.requestMousePointerLock = function() {
canvas.requestPointerLock = canvas.requestPointerLock ||
                            canvas.mozRequestPointerLock ||
                            canvas.webkitRequestPointerLock;
	canvas.requestPointerLock();
}

PlatformJs.prototype.restoreWindowCursor = function() {
	document.getElementById("main").style.cursor = "auto";
}

PlatformJs.prototype.setWindowCursor = function(hotx, hoty, sizex, sizey, imgdata, imgdatalength) {
	document.getElementById("main").style.cursor = "url(data:image/png;base64," + toBase64(imgdata) + "), auto";
}

PlatformJs.prototype.saveAssetToCache = function(tosave) {
}

// http://stackoverflow.com/a/18480879
function download(canvas, filename) {

    /// create an "off-screen" anchor tag
    var lnk = document.createElement('a'),
        e;

    /// the key here is to set the download attribute of the a tag
    lnk.download = filename;

    /// convert canvas content to data-uri for link. When download
    /// attribute is set the content pointed to by link will be
    /// pushed as "download" in HTML5 capable browsers
    lnk.href = canvas.toDataURL();

    /// create a "fake" click-event to trigger the download
    if (document.createEvent) {

        e = document.createEvent("MouseEvents");
        e.initMouseEvent("click", true, true, window,
                         0, 0, 0, 0, 0, false, false, false,
                         false, 0, null);

        lnk.dispatchEvent(e);

    } else if (lnk.fireEvent) {

        lnk.fireEvent("onclick");
    }
}

// http://stackoverflow.com/a/10632399
Number.prototype.padLeft = function(base,chr){
    var  len = (String(base || 10).length - String(this).length)+1;
    return len > 0? new Array(len).join(chr || '0')+this : this;
}
PlatformJs.prototype.saveScreenshot = function() {
var d = new Date,
    dformat = [d.getFullYear(),
               (d.getMonth()+1).padLeft(),
               d.getDate().padLeft()].join('-') +'_' +
              [d.getHours().padLeft(),
               d.getMinutes().padLeft(),
               d.getSeconds().padLeft()].join('-');
	download(canvas, "Screenshot" + dformat + ".png");
}

PlatformJs.prototype.setMatrixUniformModelView = function(mvMatrix) {
        gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}

PlatformJs.prototype.setMatrixUniformProjection = function(pMatrix) {
        gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
}

PlatformJs.prototype.setPreferences = function(preferences) {
	localStorage.setItem("Preferences", JSON.stringify(preferences.items));
}

PlatformJs.prototype.setTextRendererFont = function(fontID) {
}

PlatformJs.prototype.setTitle = function(applicationname) {
}

PlatformJs.prototype.setVSync = function(enabled) {
}

PlatformJs.prototype.setWindowState = function(value) {
}

PlatformJs.prototype.showKeyboard = function(show) {
}

PlatformJs.prototype.singlePlayerServerAvailable = function() {
	return false;
}

PlatformJs.prototype.singlePlayerServerDisable = function() {
}

PlatformJs.prototype.singlePlayerServerExit = function() {
}

PlatformJs.prototype.singlePlayerServerGetNetwork = function() {
	return null;
}

PlatformJs.prototype.singlePlayerServerLoaded = function() {
	return false;
}

PlatformJs.prototype.singlePlayerServerStart = function(saveFilename) {
}

PlatformJs.prototype.languageNativeAvailable = function() {
	return false;
}

PlatformJs.prototype.getLanguageHandler = function() {
	return new LanguageCi();
}

PlatformJs.prototype.stringContains = function(a, b) {
	return a.indexOf(b) != -1;
}

PlatformJs.prototype.stringEmpty = function(data) {
	return data == "";
}

var stringFormat = function(s, arguments) {
    var i = arguments.length;

    while (i--) {
        s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
    }
    return s;
};


PlatformJs.prototype.stringFormat = function(format, arg0) {
	return stringFormat(format, [arg0]);
}

PlatformJs.prototype.stringFormat2 = function(format, arg0, arg1) {
	return stringFormat(format, [arg0, arg1]);
}

PlatformJs.prototype.stringFormat3 = function(format, arg0, arg1, arg2) {
	return stringFormat(format, [arg0, arg1, arg2]);
}

PlatformJs.prototype.stringFormat4 = function(format, arg0, arg1, arg2, arg3) {
	return stringFormat(format, [arg0, arg1, arg2, arg3]);
}

PlatformJs.prototype.stringFromUtf8ByteArray = function(value, valueLength) {
	var arr = new Uint8Array(valueLength);
	for(var i = 0; i < valueLength;i++)
	{
		arr[i] = value[i];
	}
	var encodedString = String.fromCharCode.apply(null, arr),
	decodedString = decodeURIComponent(escape(encodedString));
	return decodedString;
}

PlatformJs.prototype.stringIndexOf = function(s, p) {
	return s.indexOf(p);
}

PlatformJs.prototype.stringReplace = function(s, from, to) {
	return s.replace(from, to);
}

PlatformJs.prototype.stringSplit = function(value, separator, returnLength) {
	var ret = value.split(separator);
	returnLength.value = ret.length;
	return ret;
}

PlatformJs.prototype.stringJoin = function(value, separator) {
	return value.join(separator);
}

PlatformJs.prototype.stringStartsWithIgnoreCase = function(a, b) {
	return a.toLowerCase().indexOf(b.toLowerCase()) == 0;
}

PlatformJs.prototype.stringToCharArray = function(s, length) {
	var ret = {};
	if (s == null)
	{
		s="";
	}
	for(var i = 0; i < s.length; i++)
	{
		ret[i] = s.charCodeAt(i);
	}
	length.value = s.length;
	return ret;
}

PlatformJs.prototype.stringToLower = function(p) {
	return p.toLowerCase();
}

// http://stackoverflow.com/a/18729931
PlatformJs.prototype.stringToUtf8ByteArray = function(s, retLength) {
	var str = s;
    var utf8 = [];
    for (var i=0; i < str.length; i++) {
        var charcode = str.charCodeAt(i);
        if (charcode < 0x80) utf8.push(charcode);
        else if (charcode < 0x800) {
            utf8.push(0xc0 | (charcode >> 6),
                      0x80 | (charcode & 0x3f));
        }
        else if (charcode < 0xd800 || charcode >= 0xe000) {
            utf8.push(0xe0 | (charcode >> 12),
                      0x80 | ((charcode>>6) & 0x3f),
                      0x80 | (charcode & 0x3f));
        }
        // surrogate pair
        else {
            i++;
            // UTF-16 encodes 0x10000-0x10FFFF by
            // subtracting 0x10000 and splitting the
            // 20 bits of 0x0-0xFFFFF into two halves
            charcode = 0x10000 + (((charcode & 0x3ff)<<10)
                      | (str.charCodeAt(i) & 0x3ff))
            utf8.push(0xf0 | (charcode >>18),
                      0x80 | ((charcode>>12) & 0x3f),
                      0x80 | ((charcode>>6) & 0x3f),
                      0x80 | (charcode & 0x3f));
        }
    }
    retLength.value = utf8.length;
    return utf8;
}

PlatformJs.prototype.stringTrim = function(value) {
	return value.trim();
}

PlatformJs.prototype.tcpAvailable = function() {
	return false;
}

PlatformJs.prototype.tcpConnect = function(ip, port, connected) {
}

PlatformJs.prototype.tcpReceive = function(data, dataLength) {
	return 0;
}

PlatformJs.prototype.tcpSend = function(data, length) {
}

PlatformJs.prototype.textSize = function(text, font, outWidth, outHeight) {
	var canvas1 = document.getElementById('textureCanvas');
	var ctx = canvas1.getContext('2d');
	setFont(ctx, text, font.size, 0);
	outWidth.value = ctx.measureText(text).width;
	outHeight.value = 2 * font.size;
}

PlatformJs.prototype.threadSpinWait = function(iterations) {
}

PlatformJs.prototype.throwException = function(message) {
}

PlatformJs.prototype.thumbnailDownloadAsync = function(ip, port, response) {
}

if (window.performance.now) {
    console.log("Using high performance timer");
    getTimestamp = function() { return window.performance.now(); };
} else {
    if (window.performance.webkitNow) {
        console.log("Using webkit high performance timer");
        getTimestamp = function() { return window.performance.webkitNow(); };
    } else {
        console.log("Using low performance timer");
        getTimestamp = function() { return new Date().getTime(); };
    }
}

var startTime = getTimestamp();

PlatformJs.prototype.timeMillisecondsFromStart = function() {
	return getTimestamp() - startTime;
}

PlatformJs.prototype.timestamp = function() {
	return null;
}

PlatformJs.prototype.webClientDownloadDataAsync = function(url, response) {
	var xhr = new XMLHttpRequest();
	xhr.open("GET", url);
	xhr.overrideMimeType('text/plain; charset=x-user-defined');
	xhr.onload  = function() {
		if (this.status == 200) {
			var ret = {};
			for (var i=0;i<xhr.response.length;i++)
			{
				ret[i]=xhr.response.charCodeAt(i) & 0xff;;
			}
			response.value = ret;
			response.valueLength = xhr.response.length;
			response.done = true;
		}
	}
	xhr.onerror = function()
		{
			response.error = true;
		}
	xhr.send();
}

PlatformJs.prototype.webClientUploadDataAsync = function(url, data, dataLength, response) {
	var xhr = new XMLHttpRequest();
	xhr.open("POST", url);

	xhr.overrideMimeType('text/plain; charset=x-user-defined');
	xhr.onload  = function() {
		if (this.status == 200) {
			var ret = {};
			for (var i=0;i<xhr.response.length;i++)
			{
				ret[i]=xhr.response.charCodeAt(i) & 0xff;;
			}
			response.value = ret;
			response.valueLength = xhr.response.length;
			response.done = true;
		}
	}
	var data2 = new Uint8Array(dataLength);
	for (var i=0; i < dataLength; i++)
	{
		data2[i] = data[i];
	}
	xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	xhr.send(data2);
}

PlatformJs.prototype.webSocketAvailable = function() {
	return true;
}

var websocket;
PlatformJs.prototype.webSocketConnect = function(ip, port) {
	websocket = new WebSocket("ws://"+ip+":"+port+"/Game");
	websocket.binaryType = "arraybuffer";
	websocket.onopen = function(evt) { onOpen(evt) };
	websocket.onclose = function(evt) { onClose(evt) };
	websocket.onmessage = function(evt) { onMessage(evt) };
	websocket.onerror = function(evt) { onError(evt) };
}

var incoming = [];

var connected = false;
function onOpen(evt)
{
connected = true;
}
function onClose(evt)
{
}
function onMessage(evt)
{
	incoming.push(evt.data);
}
function onError(evt)
{
}

var outgoing = [];

PlatformJs.prototype.webSocketSend = function(data, dataLength) {
	var data2 = new Uint8Array(dataLength);
	for(var i = 0; i < dataLength; i++)
	{
		data2[i] = data[i];
	}
	if (connected)
	{
		websocket.send(data2);
	}
	else
	{
		outgoing.push(data2);
	}
}

PlatformJs.prototype.webSocketReceive = function(data, dataLength) {
	if (connected)
	{
		while(outgoing.length!=0)
		{
			websocket.send(outgoing.shift());
		}
	}
	if (incoming.length != 0)
	{
		var packet = new Uint8Array(incoming.shift());
		for (var i = 0; i < packet.length; i++)
		{
			data[i] = packet[i];
		}
		return packet.length;
	}
	else
	{
		return -1;
	}
}

PlatformJs.prototype.windowExit = function() {
}


PlatformJs.prototype.start = function() {
}




/**
 * Provides requestAnimationFrame in a cross browser way.
 */
window.requestAnimFrame = (function() {
  return window.requestAnimationFrame ||
         window.webkitRequestAnimationFrame ||
         window.mozRequestAnimationFrame ||
         window.oRequestAnimationFrame ||
         window.msRequestAnimationFrame ||
         function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
           window.setTimeout(callback, 1000/60);
         };
})();

    var gl;

    function initGL(canvas) {
        try {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            alert("Could not initialise WebGL, sorry :-(");
        }
    }


    function getShader(gl, id) {
        var shaderScript = document.getElementById(id);
        if (!shaderScript) {
            return null;
        }

        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType == 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }

        var shader;
        if (shaderScript.type == "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type == "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }

        gl.shaderSource(shader, str);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }

        return shader;
    }


    var shaderProgram;

    function initShaders() {
        var fragmentShader = getShader(gl, "shader-fs");
        var vertexShader = getShader(gl, "shader-vs");

        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram, vertexShader);
        gl.attachShader(shaderProgram, fragmentShader);
        gl.linkProgram(shaderProgram);

        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }

        gl.useProgram(shaderProgram);

        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

        shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
        gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);

        shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
        gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);

        shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
        shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
        shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
    }

    function degToRad(degrees) {
        return degrees * Math.PI / 180;
    }

	function handleKeyDown(event) {
		if(event.keyCode == 8
			)
		{
			var args = new KeyPressEventArgs();
			args.keyChar  = 8;
			keyEventHandler.onKeyPress(args);
			event.stopPropagation();
			event.preventDefault();
		}
		var args = new KeyEventArgs();
		args.keyCode = GetKeyCode(event.keyCode);
		args.setCtrlPressed(event.ctrlKey);
		args.setShiftPressed(event.shiftKey);
		args.setAltPressed(event.altKey);
		keyEventHandler.onKeyDown(args);
		if(args.keyCode == GlKeys.F1
		|| args.keyCode == GlKeys.F2
		|| args.keyCode == GlKeys.F3
		|| args.keyCode == GlKeys.F4
		|| args.keyCode == GlKeys.F5
		|| args.keyCode == GlKeys.F6
		|| args.keyCode == GlKeys.F7
		|| args.keyCode == GlKeys.F8
		|| args.keyCode == GlKeys.F9
		|| args.keyCode == GlKeys.F10
		|| args.keyCode == GlKeys.F11
		|| args.keyCode == GlKeys.F12
		|| args.keyCode == GlKeys.TAB
		)
		{
			event.stopPropagation();
			event.preventDefault();
			return;
		}
	}

	function GetKeyCode(jsKey)
	{
		switch(jsKey)
    		{
    			case 8: return GlKeys.BACK_SPACE;
			case 9: return GlKeys.TAB;
			case 13: return GlKeys.ENTER;
			case 16: return GlKeys.SHIFT_LEFT;
			case 27: return GlKeys.ESCAPE;
			case 32: return GlKeys.SPACE;
			case 33: return GlKeys.PAGE_UP;
			case 34: return GlKeys.PAGE_DOWN;
			case 37: return GlKeys.LEFT;
			case 38: return GlKeys.UP;
			case 39: return GlKeys.RIGHT;
			case 40: return GlKeys.DOWN;
			case 48: return GlKeys.NUMBER0;
			case 49: return GlKeys.NUMBER1;
			case 50: return GlKeys.NUMBER2;
			case 51: return GlKeys.NUMBER3;
			case 52: return GlKeys.NUMBER4;
			case 53: return GlKeys.NUMBER5;
			case 54: return GlKeys.NUMBER6;
			case 55: return GlKeys.NUMBER7;
			case 56: return GlKeys.NUMBER8;
			case 57: return GlKeys.NUMBER9;
			case 65: return GlKeys.A;
			case 66: return GlKeys.B;
			case 67: return GlKeys.C;
			case 68: return GlKeys.D;
			case 69: return GlKeys.E;
			case 70: return GlKeys.F;
			case 71: return GlKeys.G;
			case 72: return GlKeys.H;
			case 73: return GlKeys.I;
			case 74: return GlKeys.J;
			case 75: return GlKeys.K;
			case 76: return GlKeys.L;
			case 77: return GlKeys.M;
			case 78: return GlKeys.N;
			case 79: return GlKeys.O;
			case 80: return GlKeys.P;
			case 81: return GlKeys.Q;
			case 82: return GlKeys.R;
			case 83: return GlKeys.S;
			case 84: return GlKeys.T;
			case 85: return GlKeys.U;
			case 86: return GlKeys.V;
			case 87: return GlKeys.W;
			case 88: return GlKeys.X;
			case 89: return GlKeys.Y;
			case 90: return GlKeys.Z;
			case 112: return GlKeys.F1;
			case 113: return GlKeys.F2;
			case 114: return GlKeys.F3;
			case 115: return GlKeys.F4;
			case 116: return GlKeys.F5;
			case 117: return GlKeys.F6;
			case 118: return GlKeys.F7;
			case 119: return GlKeys.F8;
			case 120: return GlKeys.F9;
			case 121: return GlKeys.F10;
			case 122: return GlKeys.F11;
			case 123: return GlKeys.F12;
			default: return 0;
		}
	}

	function handleKeyUp(event) {
		var args = new KeyEventArgs();
		args.keyCode = GetKeyCode(event.keyCode);
		keyEventHandler.onKeyUp(args);
	}

	function handleKeyPress(event) {
		if(event.charCode == 13)
		{
			return;
		}
		var args = new KeyPressEventArgs();
		args.keyChar  = event.charCode;
		keyEventHandler.onKeyPress(args);
	}

	function handleMouseDown(event) {
		var args = new MouseEventArgs();
		args.x = event.pageX;
		args.y = event.pageY;
		args.button = event.button;
		mouseEventHandler.onMouseDown(args);
	}

	function handleMouseUp(event) {
		var args = new MouseEventArgs();
		args.x = event.pageX;
		args.y = event.pageY;
		args.button = event.button;
		mouseEventHandler.onMouseUp(args);
	}

var lastMovementX = 0;
var lastMovementY = 0;
	function handleMouseMove(event) {
		var args = new MouseEventArgs();
		var movementX = event.movementX ||
			event.mozMovementX ||
			event.webkitMovementX ||
			0;

		var movementY = event.movementY ||
			event.mozMovementY ||
			event.webkitMovementY ||
			0;
		args.movementX = movementX;
		args.movementY = movementY;
		args.forceUsage = true;
		if (!_isMousePointerLocked())
		{
			args.x = event.pageX;
			args.y = event.pageY;
		}
		// Bug in Chrome: Sometimes it inserts additional single movement in the opposite direction.
		// To observe this problem, move mouse continuously down. Camera will sometimes look up.
		if((lastMovementX > 0 && movementX < -100)
			|| (lastMovementX < 0 && movementX > 100)
			|| (lastMovementY > 0 && movementY < -100)
			|| (lastMovementY < 0 && movementY > 100))
		{
			// Ignore
		}
		else
		{
			mouseEventHandler.onMouseMove(args);
		}
		lastMovementX = movementX;
		lastMovementY = movementY;
	}

	function handleMouseWheel(event) {
		var args = new MouseWheelEventArgs();
		args.delta = event.wheelDelta / 120;
		args.deltaPrecise = event.wheelDelta / 120;
		mouseEventHandler.onMouseWheel(args);
	}


	function handleTouchStart(event) {
		var args = new TouchEventArgs();
		for (var i = 0; i < event.changedTouches.length; i++)
		{
			var touch = event.changedTouches[i];
			args.x = touch.pageX;
			args.y = touch.pageY;
			args.id = touch.identifier;
			touchEventHandler.onTouchStart(args);
		}
		event.stopPropagation();
		event.preventDefault();
	}

	function handleTouchEnd(event) {
		var args = new TouchEventArgs();

		for (var i = 0; i < event.changedTouches.length; i++)
		{
			var touch = event.changedTouches[i];
			args.x = touch.pageX;
			args.y = touch.pageY;
			args.id = touch.identifier;
			touchEventHandler.onTouchEnd(args);
		}

		event.stopPropagation();
		event.preventDefault();
	}

	function handleTouchMove(event) {
		var args = new TouchEventArgs();
		for (var i = 0; i < event.changedTouches.length; i++)
		{
			var touch = event.changedTouches[i];
			args.x = touch.pageX;
			args.y = touch.pageY;
			args.id = touch.identifier;
			touchEventHandler.onTouchMove(args);
		}
		event.stopPropagation();
		event.preventDefault();
	}


var lastTime = 0;
var newFrameArgs = {};
newFrameArgs.dt = 0;
newFrameArgs.getDt = function() { return this.dt; }
newFrameArgs.setDt = function(dt_) { this.dt = dt_; }

var oldCanvasWidth = 0;
var oldCanvasHeight = 0;
function resizeCanvas()
{
	if (window.innerWidth != oldCanvasWidth
		|| window.innerHeight != oldCanvasHeight)
	{
		oldCanvasWidth = window.innerWidth;
		oldCanvasHeight = window.innerHeight;
		canvas.width  = window.innerWidth;
		canvas.height = window.innerHeight;
	}
}

function tick() {
	requestAnimFrame(tick);

	resizeCanvas();
	var timeNow = getTimestamp();
	if (lastTime != 0) {
		var elapsed = timeNow - lastTime;
		newFrameArgs.dt = elapsed / 1000;
	}
	lastTime = timeNow;
	newFrameHandler.onNewFrame(newFrameArgs);
}

function webGLStart() {
	canvas = document.getElementById("lesson06-canvas");

	textureCanvas = document.getElementById("textureCanvas");
	textureCanvasContext = textureCanvas.getContext("2d");
  audioContext = new AudioContext();

	initGL(canvas);
	initShaders();

	gl.clearColor(1.0, 0.0, 0.0, 1.0);
	gl.enable(gl.DEPTH_TEST);
	gl.enable(gl.BLEND);
	gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);


	document.onkeydown = handleKeyDown;
	document.onkeyup = handleKeyUp;
	document.onkeypress = handleKeyPress;
	canvas.onmousedown = handleMouseDown;
	document.onmousemove = handleMouseMove;
	document.onmousewheel = handleMouseWheel;
	canvas.onmouseup = handleMouseUp;
	document.addEventListener("touchstart", handleTouchStart, false);
	document.addEventListener("touchmove", handleTouchMove, false);
	document.addEventListener("touchend", handleTouchEnd, false);
	document.addEventListener("contextmenu", function(e) { e.preventDefault(); });

	var mainmenu = new MainMenu();
	var platform = new PlatformJs();
	mainmenu.start(platform);
	platform.start();
	tick();
}

</script>


</head>


<body onload="webGLStart();" id="main" style="padding:0px; margin:0px;">
    <canvas id="lesson06-canvas" style="border: none;"></canvas>
    <canvas id="textureCanvas" style="display: none;">I'm sorry your browser does not support the HTML5 canvas element.</canvas>
</body>

</html>
